package statefulset

import (
	"fmt"
	"strings"

	"mariadb-operator/pkg/apis/finupgroup/v1alpha1"
	"mariadb-operator/pkg/constants"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

// New will return a new Kubernetes statefulset for a maridb cluster
func New(cluster *v1alpha1.MariaDBCluster) *appsv1.StatefulSet {
	graceperiod := new(int64)
	*graceperiod = 3
	labels := map[string]string{constants.ClusterLabel: cluster.Name}
	cpu := cluster.Spec.Resources.Requests.Cpu().String()
	memory := cluster.Spec.Resources.Requests.Memory().String()
	annotations := map[string]string{"cpu": cpu, "memory": memory}
	selector := &metav1.LabelSelector{MatchLabels: labels}
	storageClassName := constants.DefaultStorageClass
	if cluster.Spec.StorageClass != "" {
		storageClassName = cluster.Spec.StorageClass
	}

	volumes := []corev1.Volume{
		corev1.Volume{
			Name:         "conf",
			VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
		},
	}
	cmv := corev1.Volume{
		Name: "config-map",
		VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
			LocalObjectReference: corev1.LocalObjectReference{},
		}},
	}
	if cluster.Spec.CMName != "" {
		cmv.VolumeSource.ConfigMap.LocalObjectReference.Name = cluster.Spec.CMName
	} else {
		cmv.VolumeSource.ConfigMap.LocalObjectReference.Name = constants.DefaultMariaDBConfigMap
	}
	volumes = append(volumes, cmv)

	statefulset := &appsv1.StatefulSet{
		TypeMeta: metav1.TypeMeta{
			Kind:       "StatefulSet",
			APIVersion: "apps/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Namespace: cluster.Namespace,
			Name:      cluster.Name,
			OwnerReferences: []metav1.OwnerReference{
				*metav1.NewControllerRef(cluster, schema.GroupVersionKind{
					Group:   v1alpha1.SchemeGroupVersion.Group,
					Version: v1alpha1.SchemeGroupVersion.Version,
					Kind:    constants.ClusterCRDResourceKind,
				}),
			},
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: appsv1.StatefulSetSpec{
			ServiceName: cluster.Name,
			Replicas:    &cluster.Spec.Replicas,
			Selector:    selector,
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: labels,
				},
				Spec: corev1.PodSpec{
					TerminationGracePeriodSeconds: graceperiod,
					InitContainers:                newInitContainers(cluster),
					Containers:                    NewContainers(cluster),
					Volumes:                       volumes,
				},
			},
			VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
				corev1.PersistentVolumeClaim{
					ObjectMeta: metav1.ObjectMeta{Name: constants.DefaultDataName},
					Spec: corev1.PersistentVolumeClaimSpec{
						StorageClassName: &storageClassName,
						AccessModes: []corev1.PersistentVolumeAccessMode{
							corev1.ReadWriteOnce,
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceStorage: resource.MustParse(cluster.Spec.StorageSize),
							},
						},
					},
				},
			},
		},
	}
	return statefulset
}

func newInitContainers(cluster *v1alpha1.MariaDBCluster) []corev1.Container {
	envs := []corev1.EnvVar{
		corev1.EnvVar{
			Name: "MYSQL_ROOT_PASSWORD",
			ValueFrom: &corev1.EnvVarSource{
				SecretKeyRef: &corev1.SecretKeySelector{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: constants.DefaultClusterSecretName,
					},
					Key: constants.SecretRootPasswordKey,
				},
			},
		},
	}

	cloneMysqlCmd := []string{
		"bash",
		"-c",
		fmt.Sprintf("set -ex\n# Skip the clone if data already exists.\n[[ -d /var/lib/mysql/mysql ]] \u0026\u0026 exit 0\n# Skip the clone on master (ordinal index 0).\n[[ `hostname` =~ -([0-9]+)$ ]] || exit 1\nordinal=${BASH_REMATCH[1]}\n[[ $ordinal -eq 0 ]] \u0026\u0026 exit 0\n# Clone data from previous peer.\nncat --recv-only %v-$(($ordinal-1)).%v 3307 | xbstream -x -C /var/lib/mysql\n# Prepare the backup.\nxtrabackup --prepare --target-dir=/var/lib/mysql\n", cluster.Name, cluster.Name),
	}

	initMysqlCmd := []string{
		"bash",
		"-c",
		"set -ex\n# Generate mysql server-id from pod ordinal index.\n[[ `hostname` =~ -([0-9]+)$ ]] || exit 1\nordinal=${BASH_REMATCH[1]}\necho [mysqld] \u003e /mnt/conf.d/server-id.cnf\n# Add an offset to avoid reserved server-id=0 value.\necho server-id=$((100 + $ordinal)) \u003e\u003e /mnt/conf.d/server-id.cnf\n# Copy appropriate conf.d files from config-map to emptyDir.\nif [[ $ordinal -eq 0 ]]; then\n  cp /mnt/config-map/master.cnf /mnt/conf.d/\nelse\n  cp /mnt/config-map/slave.cnf /mnt/conf.d/\nfi\n",
	}

	cloneMysqlVms := []corev1.VolumeMount{
		corev1.VolumeMount{
			Name:      constants.DefaultDataName,
			MountPath: "/var/lib/mysql",
			SubPath:   "mysql",
		},
		corev1.VolumeMount{
			Name:      "conf",
			MountPath: "/etc/mysql/conf.d",
		},
	}

	initMysqlVms := []corev1.VolumeMount{
		corev1.VolumeMount{
			Name:      "conf",
			MountPath: "mnt/conf.d",
		},
		corev1.VolumeMount{
			Name:      "config-map",
			MountPath: "/mnt/config-map",
		},
	}

	initMysql := corev1.Container{
		Name:         "init-mysql",
		Image:        cluster.Spec.Image,
		Command:      initMysqlCmd,
		Env:          envs,
		VolumeMounts: initMysqlVms,
	}

	cloneMysql := corev1.Container{
		Name:         "clone-mysql",
		Image:        "xtrabackup:1.0",
		Command:      cloneMysqlCmd,
		Env:          envs,
		VolumeMounts: cloneMysqlVms,
	}
	return []corev1.Container{initMysql, cloneMysql}
}

func NewContainers(cluster *v1alpha1.MariaDBCluster) []corev1.Container {
	envs := []corev1.EnvVar{
		corev1.EnvVar{
			Name: "MYSQL_ROOT_PASSWORD",
			ValueFrom: &corev1.EnvVarSource{
				SecretKeyRef: &corev1.SecretKeySelector{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: constants.DefaultClusterSecretName,
					},
					Key: constants.SecretRootPasswordKey,
				},
			},
		},
		corev1.EnvVar{
			Name: "MYSQL_PASSWORD",
			ValueFrom: &corev1.EnvVarSource{
				SecretKeyRef: &corev1.SecretKeySelector{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: constants.DefaultClusterSecretName,
					},
					Key: constants.SecretPasswordKey,
				},
			},
		},
		corev1.EnvVar{
			Name: "MYSQL_USER",
			ValueFrom: &corev1.EnvVarSource{
				SecretKeyRef: &corev1.SecretKeySelector{
					LocalObjectReference: corev1.LocalObjectReference{
						Name: constants.DefaultClusterSecretName,
					},
					Key: constants.SecretUserKey,
				},
			},
		},
		corev1.EnvVar{
			Name:  "MYSQL_DATABASE",
			Value: cluster.Spec.DatabaseName,
		},
	}

	mysql := corev1.Container{
		Name:      constants.MariaDBContainerName,
		Image:     cluster.Spec.Image,
		Resources: cluster.Spec.Resources,
		Env:       envs,
		LivenessProbe: &corev1.Probe{
			Handler: corev1.Handler{
				Exec: &corev1.ExecAction{
					Command: []string{"/bin/sh", "-c", "MYSQL_PWD=\"$MYSQL_ROOT_PASSWORD\"", "mysql", "-p$(MYSQL_ROOT_PASSWORD)", "-e", "SELECT 1"},
				},
			},
			InitialDelaySeconds: 30,
			PeriodSeconds:       10,
			TimeoutSeconds:      5,
		},
		ReadinessProbe: &corev1.Probe{
			Handler: corev1.Handler{
				Exec: &corev1.ExecAction{
					Command: []string{"/bin/sh", "-c", "MYSQL_PWD=\"$MYSQL_ROOT_PASSWORD\"", "mysqladmin", "ping"},
				},
			},
			InitialDelaySeconds: 5,
			PeriodSeconds:       2,
			TimeoutSeconds:      1,
		},
		VolumeMounts: []corev1.VolumeMount{
			corev1.VolumeMount{
				Name:      constants.DefaultDataName,
				MountPath: "/var/lib/mysql",
				SubPath:   "mysql",
			},
			corev1.VolumeMount{
				Name:      "conf",
				MountPath: "/etc/mysql/conf.d",
			},
		},
	}

	cmd := fmt.Sprintf("set -ex\ncd /var/lib/mysql\n# Determine binlog position of cloned data, if any.\nif [[ -f xtrabackup_slave_info ]]; then\n  # XtraBackup already generated a partial \"CHANGE MASTER TO\" query\n  # because we're cloning from an existing slave.\n  cp xtrabackup_slave_info change_master_to.sql.in\n  # Ignore xtrabackup_binlog_info in this case (it's useless).\n  # rm -f xtrabackup_binlog_info\nelif [[ -f xtrabackup_binlog_info ]]; then\n  # We're cloning directly from master. Parse binlog position.\n  [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)[[:space:]]+(.*?)$ ]] || exit 1\n  # rm xtrabackup_binlog_info\n  echo \"CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\\\n        MASTER_LOG_POS=${BASH_REMATCH[2]}\" \u003e change_master_to.sql.in\nfi\n# Check if we need to complete a clone by starting replication.\nif [[ -f change_master_to.sql.in ]]; then\n  echo \"Waiting for mysqld to be ready (accepting connections)\"\n  until mysql -h 127.0.0.1 -p${MYSQL_ROOT_PASSWORD} -e \"SELECT 1\"; do sleep 1; done\n  echo \"Initializing replication from clone position\"\n  # In case of container restart, attempt this at-most-once.\n  mv change_master_to.sql.in change_master_to.sql.orig\n  mysql -h 127.0.0.1 -p${MYSQL_ROOT_PASSWORD} \u003c\u003cEOF\nSTOP SLAVE;\n$(\u003cchange_master_to.sql.orig),\n  MASTER_HOST='%v-0.%v',\n  MASTER_USER='root',\n  MASTER_PASSWORD=\"${MYSQL_ROOT_PASSWORD}\",\n  MASTER_CONNECT_RETRY=10;\nSTART SLAVE;\nEOF\nfi\n# Start a server to send backups when requested by peers.\nexec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \\\n  \"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root --password=${MYSQL_ROOT_PASSWORD}\"\n", cluster.Name, cluster.Name)
	if cluster.Spec.DataFrom != "" {
		dataFromFile := cluster.Spec.DataFrom[strings.LastIndex(cluster.Spec.DataFrom, "/"):]
		cmd = fmt.Sprintf("set -ex\ncd /var/lib/mysql\nif [[ -f xtrabackup_slave_info ]]; then\n  cp xtrabackup_slave_info change_master_to.sql.in\n  rm -f xtrabackup_binlog_info\nelif [[ -f xtrabackup_binlog_info ]]; then\n  [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)[[:space:]]+(.*?)$ ]] || exit 1\n  rm xtrabackup_binlog_info\n  echo \"CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\\\n        MASTER_LOG_POS=${BASH_REMATCH[2]}\" \u003e change_master_to.sql.in\nfi\nif [[ -f change_master_to.sql.in ]]; then\n  echo \"Waiting for mysqld to be ready (accepting connections)\"\n  until mysql -h 127.0.0.1 -p${MYSQL_ROOT_PASSWORD} -e \"SELECT 1\"; do sleep 1; done\n  echo \"Initializing replication from clone position\"\n  mv change_master_to.sql.in change_master_to.sql.orig\n  mysql -h 127.0.0.1 -p${MYSQL_ROOT_PASSWORD} \u003c\u003cEOF\nSTOP SLAVE;\n$(\u003cchange_master_to.sql.orig),\n  MASTER_HOST='%v-0.%v',\n  MASTER_USER='root',\n  MASTER_PASSWORD=\"${MYSQL_ROOT_PASSWORD}\",\n  MASTER_CONNECT_RETRY=10;\nSTART SLAVE;\nEOF\nelse\n  echo \"Waiting for mysqld to be ready (accepting connections)\"\n  until mysql -h 127.0.0.1 -p${MYSQL_ROOT_PASSWORD} -e \"SELECT 1\"; do sleep 1; done\n  cd / \n  i=1\n  while [ ${i} -le 5 ]\n  do\n    echo \"--------------- download begin ---------------\"\n    wget %v \n    if [ $? -eq 0 ]; then\n        echo \"--------------- download complete ---------------\"\n        break;\n    else\n        echo \"...............error occur, retry in 2 seconds ..........\"\n        sleep 2\n    fi\n    ((i++))\n  done\n  gunzip < %v | mysql -h 127.0.0.1 -uroot -p${MYSQL_ROOT_PASSWORD};\nfi\nexec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \\\n  \"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root --password=${MYSQL_ROOT_PASSWORD}\"\n", cluster.Name, cluster.Name, cluster.Spec.DataFrom, dataFromFile)
	}
	xtrabackupCmd := []string{
		"bash",
		"-c",
		cmd,
	}

	xtrabackup := corev1.Container{
		Name:  constants.MariaDBXtrabackupContainerName,
		Image: constants.DefaultXtrabackupImage,
		Env:   envs,
		Resources: corev1.ResourceRequirements{
			Requests: corev1.ResourceList{
				corev1.ResourceCPU:    resource.MustParse("100m"),
				corev1.ResourceMemory: resource.MustParse("100Mi"),
			},
		},
		Command: xtrabackupCmd,
		VolumeMounts: []corev1.VolumeMount{
			corev1.VolumeMount{
				Name:      constants.DefaultDataName,
				MountPath: "/var/lib/mysql",
				SubPath:   "mysql",
			},
			corev1.VolumeMount{
				Name:      "conf",
				MountPath: "/etc/mysql/conf.d",
			},
		},
	}

	if cluster.Spec.Monitor {
		exporter := corev1.Container{
			Name:  constants.MariaDBExporterContainerName,
			Image: constants.DefaultExporterImage,
			Resources: corev1.ResourceRequirements{
				Requests: corev1.ResourceList{
					corev1.ResourceCPU:    resource.MustParse("500m"),
					corev1.ResourceMemory: resource.MustParse("500Mi"),
				},
			},
			// LivenessProbe: &corev1.Probe{
			// 	Handler: corev1.Handler{
			// 		HTTPGet: &corev1.HTTPGetAction{
			// 			Host:   "127.0.0.1",
			// 			Port:   intstr.FromInt(constants.MariaDBExporterContainerPort),
			// 			Path:   "/metrics",
			// 			Scheme: corev1.URISchemeHTTP,
			// 		},
			// 	},
			// 	InitialDelaySeconds: 30,
			// 	PeriodSeconds:       10,
			// 	TimeoutSeconds:      5,
			// },
			// ReadinessProbe: &corev1.Probe{
			// 	Handler: corev1.Handler{
			// 		HTTPGet: &corev1.HTTPGetAction{
			// 			Host:   "127.0.0.1",
			// 			Port:   intstr.FromInt(constants.MariaDBExporterContainerPort),
			// 			Path:   "/metrics",
			// 			Scheme: corev1.URISchemeHTTP,
			// 		},
			// 	},
			// 	InitialDelaySeconds: 30,
			// 	PeriodSeconds:       10,
			// 	TimeoutSeconds:      5,
			// },
			Ports: []corev1.ContainerPort{
				corev1.ContainerPort{
					Name:          constants.MariaDBExporterContainerName,
					ContainerPort: constants.MariaDBExporterContainerPort,
				},
			},
			Env: []corev1.EnvVar{
				corev1.EnvVar{
					Name: "MYSQL_ROOT_PASSWORD",
					ValueFrom: &corev1.EnvVarSource{
						SecretKeyRef: &corev1.SecretKeySelector{
							LocalObjectReference: corev1.LocalObjectReference{
								Name: constants.DefaultClusterSecretName,
							},
							Key: constants.SecretRootPasswordKey,
						},
					},
				},
				corev1.EnvVar{
					Name:  "DATA_SOURCE_NAME",
					Value: "root:$MYSQL_ROOT_PASSWORD@(127.0.0.1:3306)/",
				},
			},
		}
		return []corev1.Container{mysql, xtrabackup, exporter}
	}
	return []corev1.Container{mysql, xtrabackup}
}
